#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <cstring>
#include "Texture.h"

#define clampU(x,y) (x)>(y)?(y):(x)
#define clampL(x,y) (x)<(y)?(y):(x)

using namespace std;

void marchRay(Ray &r, Texture &hmap, Texture &nmap, float maxDist);

Texture grass(2048, 2048);
Texture gravel(2048, 2048);
Texture gColor(1024, 1024);
VecRGB fogColor(0.549f, 0.667f, 0.752f);

inline float calcOcclusion(Texture &hmap, Vec3D origin, Vec3D dir, float jitter, int samples)
{
	int hits = 0;
	Ray lr;
	for(int i = 0; i < samples; i++)
	{
		lr.length = 0.0f;
		lr.pos = origin;
		lr.dir = dir.Jitter(jitter);
		lr.mode = 1;
		marchRay(lr, hmap, hmap, 30.0f);
		if(lr.finite) hits++;
	}
	return (float)hits / (float)samples;
}

inline float calcOcclusionAO(Texture &hmap, Vec3D origin, Vec3D dir, Vec3D sun, float jitter, int samples)
{
	float hits = 0;
	Ray lr;
	for(int i = 0; i < samples; i++)
	{
		lr.length = 0.0f;
		lr.pos = origin;
		lr.dir = dir.JitterAO(jitter);
		lr.mode = 1;
		marchRay(lr, hmap, hmap, 30.0f);
		if(lr.finite)
			hits++;
		else
		{
			hits += (1.0f - clampL(lr.dir.Dot(sun), 0.0f));
		}
	}
	return (float)hits / (float)samples;
}

VecRGB groundShader(Vec3D &pos, Vec3D &surfNorm, float direct, float ambient, float distance)
{
	static const Vec3D up(0.0f, 1.0f, 0.0f);
	static const VecRGB red(1.0f, 0.0f, 0.0f);
	VecRGB albedo = grass.sampleBil(pos.X / 50.0f, pos.Z / 50.0f);
	VecRGB grassColor = gColor.sampleBil(pos.X / 1024.0f, pos.Z / 1024.0f);
	albedo.colorize(grassColor);
	float steepness = 1.0f - abs(up.Dot(surfNorm));

	if(steepness > 0.2f)
	{
		VecRGB albedo2 = gravel.sampleBil(pos.X / 200.0f, pos.Z / 200.0f);
		steepness -= 0.2f;
		steepness *= 10.0f;
		steepness = clampU(steepness, 1.0f);
		albedo = lerp(albedo, albedo2, steepness);
		//albedo.set(steepness, steepness, steepness);
	}

	albedo *= (direct * 0.6f + ambient * 0.4f);
	//albedo *= (direct + (ambient * (1.0f - direct) * 0.5f));
	albedo = lerp(albedo, fogColor, distance / 1024.0f);
	return albedo;
}

void marchRay(Ray &r, Texture &hmap, Texture &nmap, float maxDist)
{
	float stepSize = r.dir.Length();
	Vec3D sunDir(4.0f, 1.0f, 3.0f);
	Vec3D aoDir(0.0f, 1.0f, 0.0f);
	sunDir.NormalizeIP();
	int refSteps = 8;
	int lmax = 50;
	int aomax = 100;

	while(r.length < maxDist)
	{
		float h = hmap.sampleBilGS(r.pos.X, r.pos.Z);
		if(h > r.pos.Y)
		{
			if(r.mode == 0 && refSteps > 0)
			{
				r.pos -= r.dir;
				r.length -= stepSize;
				r.dir *= 0.5f;
				stepSize = r.dir.Length();
				refSteps--;
				continue;
			}

			Ray lr;

			if(r.mode == 0)
			{
				Vec3D origin = r.pos;
				origin.Y = h + 0.0001f;
				float sunlight = 1.0f - calcOcclusion(hmap, origin, sunDir, 0.2f, lmax);
				float ambient = 0.0f;
				Vec3D normal = nmap.sampleBil(r.pos.X / (float)nmap.width, r.pos.Z / (float)nmap.height).toVec3D();

				ambient = 1.0f - calcOcclusionAO(hmap, origin, normal, sunDir, 3.0f, aomax) * 1.0f;
				ambient = clampL(ambient, 0.0f);

				r.color = groundShader(origin, normal, sunlight, ambient, r.length);
			}
			else
			{
				r.color.R = r.color.G = r.color.B = 0.0f;
			}

			r.finite = true;
			return;
		}
		r.pos += r.dir;
		r.length += stepSize;
	}
	//r.color.R = r.color.G = r.color.B = 0.0f;
	r.color = fogColor * 1.5f;
	r.finite = false;
}

void render(Ray *rays, Texture &hmap, Texture &nmap, Texture &fbuffer)
{
	int tenth = fbuffer.height / 10;
	printf("Rendering... ");
	for(int y = 0; y < fbuffer.height; ++y)
	{
		if(y % tenth == 0) printf("%d0%% ", y / tenth);
		for(int x = 0; x < fbuffer.width; ++x)
		{
			marchRay(rays[y * fbuffer.width + x], hmap, nmap, 800.0f);
			fbuffer.setPixel(x, y, rays[y * fbuffer.width + x].color);
		}
	}
	printf("Done!\n");
}

int main()
{
	int startTime = time(0);
	Ray *rays = RayUtils::generateRays(Vec3D(680,70,0), Vec3D(-3,-10,40), Vec3D(-3,40,10), Vec3D(10,0,3), Vec2Di(1280,720), 1.0f, 1.0f);
	//printf("Hello world!\n");
	Texture ptex(1024, 1024);
	Texture fb(1280,720);
	Texture nmap(1,1);

	srand(4747);
	ptex.generatePerlin(0.5f);

	gColor.generatePerlin(0.8f);
	gColor.normalize();
	gColor.scale(6.0f);
	gColor.subClamp(3.0f, 0.0f);
	gColor.clampToOne();
	//gColor.saveBMP("grass-color1.bmp");
	gColor.mapGradient(VecRGB(89.0f / 255.0f * 1.5f, 105.0f / 255.0f * 1.5f, 27.0f / 255.0f * 1.5f), //VecRGB(0.424f * 2, 0.463f * 2, 0.157f * 2),
			VecRGB(149.0f / 255.0f * 1.5f, 135.0f / 255.0f * 1.5f, 60.0f / 255.0f * 1.5f));
	//gColor.saveBMP("grass-color.bmp");
	//ptex.generateMips();

	for(int i = 0; i < 1; i++) ptex.Erode(100.0f);
	ptex.scale(50.0f);
	//ptex.normalize();
	//ptex.saveBMP("hmap.bmp");

	nmap = ptex.generateNormalMap();
	//nmap.saveBMP("normalMap.bmp");

	//grass.generatePerlin(2.0f);
	grass.generateWhiteNoise(1.0f, 0.0f);
	grass.generateMips();
	grass = *(grass.nextMip);
	grass = *(grass.nextMip);
	grass.freeMips();

	//grass.colorize(VecRGB(0.424f * 2, 0.463f * 2, 0.157f * 2));
	grass.generateMips();
	grass.saveBMP("grass.bmp");

	//gravel.generateWhiteNoise(1.0f, 0.0f);
	gravel.generatePerlin(1.5f);
	gravel.generateMips();
	gravel = *(gravel.nextMip);
	//gravel = *(gravel.nextMip);
	gravel.normalize();
	gravel.scale(0.35f);
	gravel.offset(0.5f);
	gravel.freeMips();
	gravel.generateMips();
	gravel.saveBMP("gravel.bmp");
	printf("Grass done");

	printf("start render\n");
	render(rays, ptex, nmap, fb);
	fb.saveBMP("render.bmp");
	printf("Render done\n");
	printf("Secs elapsed : %ld\n", time(0)-startTime);
    return 0;
}
